package com.eugenes.design;

import com.eugenes.design.cardvalidator.CardValidator;
import com.eugenes.design.cardvalidator.CompositeCardValidator;
import com.eugenes.design.cardvalidator.CompositeSoftCardValidator;
import com.eugenes.design.cardvalidator.DateTimeCardValidator;
import com.eugenes.design.cardvalidator.MultipleCountCardValidator;
import com.eugenes.util.date.DateTimeFieldValidator;
import com.eugenes.util.date.DateTimeValidator;
import com.eugenes.util.date.TimePeriodValidator;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;

/**
 * Thinking about SkiPassCardFactory.
 *
 * Sure, we can just provide API for
 * building cards for our system. E.g. CardBuilder
 * that can be used like that:
 *
 * SkiPassSystem spSystem = new SkiPassSystem();
 * CardBuilder cardBuilder = spSystem.getBuilder();
 * cardBuilder.withWeekendValidator()
 *            .withHoursValidator(9,13)
 *            .withWeekValidator()
 *            .withYearValidator();
 * Card card = cardBuilder.build();
 *
 * But task provides information about
 * only limited types of card so maybe API is
 * somewhat different from mentioned above.
 *
 * @author Yevhen_Shevchuk
 */
public class SkiPassCardFactory {

    static final int F_HALFDAY_START = 9;
    static final int F_HALFDAY_DURATION = 4 /* hours */;

    static final int S_HALFDAY_START = 13;
    static final int S_HALFDAY_DURATION = 4 /* hours */;

    SkiPassSystem system;
    int cardIdCounter = Integer.MIN_VALUE;

    MultipleCountCardValidator countCV = new MultipleCountCardValidator();

    SkiPassCardFactory(SkiPassSystem system) {
        this.system = system;
    }

    private SimpleCard getNewSystemCard(CompositeCardValidator validator) {
        CardValidator systemCV = system.getSystemCardValidator();
        if (systemCV != null) {
            validator.addValidator(systemCV);
        }
        return new SimpleCard(cardIdCounter++, validator);
    }

    private void addCurrentValueDateTimeValidator(DateTimeFieldType type,
            CompositeCardValidator validator) {
        DateTime dt = new DateTime();
        int value = dt.get(type);
        DateTimeFieldValidator thisValueTV = new DateTimeFieldValidator(type, value);
        DateTimeCardValidator cV = new DateTimeCardValidator(thisValueTV);

        validator.addValidator(cV);
    }

    private void addThisWeekyearValidator(CompositeCardValidator validator) {
        addCurrentValueDateTimeValidator(DateTimeFieldType.weekyear(), validator);
    }

    private void addThisWeekOfYearValidator(CompositeCardValidator validator) {
        addCurrentValueDateTimeValidator(DateTimeFieldType.weekOfWeekyear(), validator);
    }

    public Card getHalfDayCard(DayType dayType, HalfDays halfDay) {
        CompositeCardValidator validator = new CompositeCardValidator();
        validator.addValidator(dayType.getValidator());
        validator.addValidator(halfDay.getValidator());

        return getNewSystemCard(validator);
    }

    public Card getWeekCard(DayType dayType) {
        CompositeCardValidator validator = new CompositeCardValidator();
        validator.addValidator(dayType.getValidator());
        addThisWeekyearValidator(validator);
        addThisWeekOfYearValidator(validator);

        return getNewSystemCard(validator);
    }

    public Card getDueToCard(DateTime dueTo) {
        DateTime currentTime = new DateTime();
        if (currentTime.isAfter(dueTo)) {
            throw new IllegalArgumentException("DueTo time is invalid. dueTo<sysdate");
        }
        DateTimeValidator dtValidator = new TimePeriodValidator(currentTime, dueTo);
        CompositeCardValidator cardValidator = new CompositeCardValidator();
        cardValidator.addValidator(new DateTimeCardValidator(dtValidator));

        return getNewSystemCard(cardValidator);
    }

    public Card getCountCard(Counts counts) {
        CompositeCardValidator cardValidator = new CompositeCardValidator();
        cardValidator.addValidator(countCV);

        Card card = getNewSystemCard(cardValidator);
        countCV.addCard(card, counts.getCounts());
        return card;
    }

    public Card getCardWithValidator(CardValidator cValidator) {
        CompositeCardValidator cardValidator = new CompositeCardValidator();
        cardValidator.addValidator(cValidator);

        return getNewSystemCard(cardValidator);
    }

    public Card getCardWithDateTimeValidator(DateTimeValidator dtValidator) {
        CompositeCardValidator cardValidator = new CompositeCardValidator();
        cardValidator.addValidator(new DateTimeCardValidator(dtValidator));

        return getNewSystemCard(cardValidator);
    }

    public Card getSeasonCard() {
        CompositeCardValidator seasonValidator = new CompositeSoftCardValidator();
        DateTime currentTime = new DateTime();
        int currentMonth = currentTime.getMonthOfYear();
        int seasonStart = (currentMonth - 1) / 3 * 3; // some magic!
        int seasonEnd = seasonStart + 3; // some more magic!

        for (int month = seasonStart; month <= seasonEnd; ++month) {
            DateTimeValidator monthValidator =
                    new DateTimeFieldValidator(DateTimeFieldType.monthOfYear(), month);
            seasonValidator.addValidator(new DateTimeCardValidator(monthValidator));
        }

        CompositeCardValidator cardValidator = new CompositeCardValidator();
        cardValidator.addValidator(seasonValidator);

        return getNewSystemCard(cardValidator);
    }

    private static DateTimeValidator getHalfDayDateTimeValidator(int startHour, int duration) {
        DateTime curTime = new DateTime();
        DateTime from = curTime.withHourOfDay(startHour);
        DateTime to = from.plusHours(duration);
        DateTimeValidator hdValidator = new TimePeriodValidator(from, to);
        return hdValidator;
    }

    public enum DayType {
        WORKDAYS(DateTimeFieldValidator.getWorkDaysDateValidator()),
        WEEKEND(DateTimeFieldValidator.getWeekendDateValidator());

        CardValidator validator;

        DayType(DateTimeValidator dtValidator) {
            validator = new DateTimeCardValidator(dtValidator);
        }

        CardValidator getValidator() {
            return validator;
        }
    }

    public enum HalfDays {
        FIRST(getHalfDayDateTimeValidator(F_HALFDAY_START, F_HALFDAY_DURATION)),
        SECOND(getHalfDayDateTimeValidator(S_HALFDAY_START, S_HALFDAY_DURATION));

        CardValidator validator;

        HalfDays(DateTimeValidator dtValidator) {
            validator = new DateTimeCardValidator(dtValidator);
        }

        CardValidator getValidator() {
            return validator;
        }
    }

    public enum Counts {
        TEN(10), TWENTY(20), FIFTY(50), HUNDRED(100);

        int counts;

        Counts(int counts) {
            this.counts = counts;
        }

        int getCounts() {
            return counts;
        }
    }
}
